from bs4 import BeautifulSoup
from urllib.request import Request
from http.client import HTTPResponse
from xml.etree import ElementTree

from .liteTool import firefox_cookie, my_urlopen

import gzip
import zlib
import json


def get_av_vid_comment_number_and_tags_from_bv(bv: str) -> (str, str, int, list):
    """
    抓取一般视频的数据，电影和番剧我还没研究过。
    :param bv: B站视频的BV号
    :return: 第一个是str形式的AV号（没有AV前缀），第二个是获取弹幕要用的一个id（姑且叫它vid），
             第三个是视频评论数（包括回复），第四个是视频标签列表
    """
    url = f'https://www.bilibili.com/video/{bv}'
    headers = {
        'Host': 'www.bilibili.com',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Cookie': firefox_cookie,
        'Upgrade-Insecure-Requests': '1',
        'Cache-Control': 'max-age=0',
        'TE': 'Trailers',
    }

    try:
        print(f'获取视频页面：{url}')
        request = Request(url=url, headers=headers, method='GET')
        response = my_urlopen(url=request)  # type: HTTPResponse
        response_data = gzip.decompress(response.read()).decode(encoding='utf-8')

        bs = BeautifulSoup(markup=response_data, features='html.parser')

        head = bs.find(name='head')
        scripts = head.find_all(name='script')
        script_vid = scripts[2]
        script_tag = scripts[3]

        video_info_raw = script_vid.string  # type: str
        video_info_raw = video_info_raw[video_info_raw.find('{'):]
        video_info_json = json.loads(video_info_raw)
        base_url = video_info_json.get('data').get('dash').get('video')[0].get('baseUrl')  # type: str
        vid = base_url.split('/')[6]

        tag_raw = script_tag.string  # type: str
        tag_raw = tag_raw[tag_raw.find('{'):tag_raw.find(';(function')]
        tag_json = json.loads(tag_raw)

        comment_meta = bs.find(name='meta', attrs={'itemprop': 'commentCount'})
        av_meta = bs.find(name='meta', attrs={'property': 'og:url'})

        comment_count = int(comment_meta.attrs['content'])
        av_number = av_meta.attrs['content'].split('av')[-1][:-1]

        return av_number, vid, comment_count, tag_json.get('tags')
    except Exception as e:
        print(e)
        return '', '', -1, ''


def get_comments_and_replies_from_av_and_bv(av: str, bv: str, comment_total: int = -1, by_time: bool = False) -> list:
    """
    获取评论和评论下面的回复
    :param av: AV号，没有AV前缀
    :param bv: BV号，需要有BV前缀
    :param comment_total: 需要获取的评论数
    :param by_time: 是否按时间顺序排列
    :return: 评论列表，评论的回复在每一条评论的replies属性中
    """
    results = []
    page_index = 1
    comment_count = 0

    def __get_json_data(url: str, header: dict, method: str) -> list:
        """
        获取评论和获取回复的过程雷同，写一个函数代替一下
        :param url: URL
        :param header: 请求头部
        :param method: 请求方法
        :return: 处理成JSON格式返回
        """
        print('获取评论数据：' + url)
        try:
            request = Request(url=url, headers=header, method=method)
            response = my_urlopen(url=request)  # type: HTTPResponse
            data_str = gzip.decompress(response.read()).decode(encoding='utf-8')
            data_json = json.loads(data_str)
            data_json = data_json.get('data').get('replies')
        except Exception as e:
            print(e)
            return []
        return data_json

    while True:
        comment_url = f'https://api.bilibili.com/x/v2/reply?type=1&pn={page_index}&oid={av}&sort={0 if by_time else 2}'
        comment_header = {
            'Host': 'api.bilibili.com',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0',
            'Accept': '*/*',
            'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
            'Accept-Encoding': 'gzip, deflate, br',
            'Connection': 'keep-alive',
            'Referer': f'https://www.bilibili.com/video/{bv}',
            'Cookie': firefox_cookie,
            'TE': 'Trailers',
        }
        comment_json = __get_json_data(url=comment_url, header=comment_header, method='GET')

        if comment_json is None or len(comment_json) == 0:
            break

        comment_count += len(comment_json)

        if 0 < comment_total <= comment_count:
            break

        for comment in comment_json:
            if comment.get('replies'):
                comment['replies'] = []

                reply_id = comment.get('rpid')
                reply_num = 10
                reply_page = 1

                while True:
                    reply_url = f'https://api.bilibili.com/x/v2/reply/reply?type=1&pn={reply_page}&oid={av}&ps=' + \
                                f'{reply_num}&root={reply_id}'
                    reply_json = __get_json_data(url=reply_url, header=comment_header, method='GET')

                    if reply_json is None or len(reply_json) == 0:
                        break

                    comment_count += len(reply_json)
                    comment['replies'] += reply_json

                    if 0 < comment_total <= comment_count or len(reply_json) < reply_num:
                        break

                    reply_page += 1

        if 0 < comment_total <= comment_count:
            break
        results += comment_json
        page_index += 1

    return results


def get_dm_from_vid_and_bv(vid: str, bv: str) -> list:
    """
    获取视频评论
    :param vid: 跟弹幕文件和视频文件有关的一个东西，我姑且叫它vid
    :param bv: BV号，要有BV前缀
    :return: 弹幕列表，每一个弹幕是一个二元组，弹幕内容和弹幕属性
    """
    def __deflate(s: bytes) -> str:
        """
        弹幕文件是用deflate加密的
        :param s: 加密前的bytes
        :return: 解密后的Unicode字符串
        """
        try:
            return zlib.decompress(s, -zlib.MAX_WBITS).decode(encoding='utf-8')
        except zlib.error as ze:
            print(ze)
            return zlib.decompress(s).decode(encoding='utf-8')

    url = f'https://api.bilibili.com/x/v1/dm/list.so?oid={vid}'
    header = {
        'Host': 'api.bilibili.com',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0',
        'Accept': '*/*',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding': 'gzip, deflate, br',
        'Origin': 'https://www.bilibili.com',
        'Connection': 'keep-alive',
        'Referer': f'https://www.bilibili.com/video/{bv}',
        'Cookie': firefox_cookie,
        'TE': 'Trailers',
    }
    try:
        print('获取弹幕文件：' + url)
        request = Request(url=url, headers=header, method='GET')
        response = my_urlopen(url=request)  # type: HTTPResponse
        xml_str = __deflate(response.read())
    except Exception as e:
        print(e)
        return []

    tree = ElementTree.fromstring(xml_str)
    res = []

    for child in tree:  # type: ElementTree.Element
        if child.tag == 'd':
            res.append((child.text, child.attrib['p']))

    return res


if __name__ == '__main__':
    """
    使用示例
    """
    BV = 'BV1CT4y1g7Ya'  # 狂战士预告
    AV, VID, comment_num, tags = get_av_vid_comment_number_and_tags_from_bv(bv=BV)
    comments_and_replies = get_comments_and_replies_from_av_and_bv(av=AV, bv=BV, comment_total=comment_num)
    dm_list = get_dm_from_vid_and_bv(vid=VID, bv=BV)

    with open(file='D:/MyResources/爬虫数据/哔哩哔哩干杯/狂战士评论.json', mode='w', encoding='utf-8') as f:
        j = json.dumps(comments_and_replies, ensure_ascii=False)
        f.write(j)

    with open(file='D:/MyReSources/爬虫数据/哔哩哔哩干杯/狂战士弹幕.txt', mode='w', encoding='utf-8') as f:
        for i in dm_list:
            f.write(i[0] + ' - ' + i[1] + '\n')

    for i in dm_list:
        print(i[0])
    print()

    for i in comments_and_replies:
        print(i['content']['message'])
        if i.get('replies'):
            for j in i['replies']:
                print('    ' + j['content']['message'])
        print()
